/**
  * Copyright 2014 Dropbox, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *    http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */

package djinni

import djinni.ast.Record.DerivingType
import djinni.ast._
import djinni.generatorTools._
import djinni.meta._
import djinni.syntax.Error
import djinni.writer.IndentWriter

import scala.collection.mutable
import scala.collection.parallel.immutable

class ReactNativeObjcGenerator(spec: Spec, objcInterfaces : Seq[String]) extends ObjcGenerator(spec) {

  class ReactNativeRefs() {
    var body = mutable.TreeSet[String]()
    var header = mutable.TreeSet[String]()

    def find(ty: TypeRef, importRCT: Boolean = false) { find(ty.resolved, importRCT) }
    def find(tm: MExpr, importRCT: Boolean) {
      tm.args.foreach(t => find(t, importRCT))
      find(tm.base, importRCT)
    }
    def find(m: Meta, importRCT: Boolean) = for(r <- marshal.reactReferences(m)) r match {
      case ImportRef(arg) => {
        header.add("#import " + arg)
        if (importRCT && !isEnum(MExpr(m, List())) && arg.indexOf("\"") == 0) {
          val rctHeader = s""""${spec.reactNativeTypePrefix}${arg.substring(1)}"""
          header.add("#import " + rctHeader)
        }
      }
      case DeclRef(decl, _) => header.add(decl)
    }
  }

  override def generate(idl: Seq[TypeDecl]): Unit = {

    super.generate(idl)

    //Create header file for base class modules
    val baseModuleName = spec.reactNativeTypePrefix + "BaseModule"
    val baseModuleFileNameHeader = baseModuleName + ".h"
    createFile(spec.reactNativeObjcOutFolder.get, baseModuleFileNameHeader, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")
      w.wl
      w.wl("#import <Foundation/Foundation.h>")
      w.wl("#import <React/RCTBridgeModule.h>")
      w.wl
      w.wl(s"@interface $baseModuleName : NSObject")
      w.wl("@property (nonatomic, strong) NSMutableDictionary *objcImplementations;")
      w.wl("@property (nonatomic, strong) NSMutableDictionary *implementationsData;")
      w.wl("-(void)baseRelease:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;")
      w.wl("-(void)baseLogWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;")
      w.wl("-(void)baseFlushWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;")
      w.wl("-(void)baseIsNull:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;")
      w.wl("-(void)baseSetObject:(NSArray *)object;")
      w.wl("@end")
    })

    //Create implementation file for base class modules
    val baseModuleFileNameImpl = baseModuleName + ".m"
    createFile(spec.reactNativeObjcOutFolder.get, baseModuleFileNameImpl, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")
      w.wl
      w.wl(s"""#import "$baseModuleFileNameHeader"""")

      w.wl
      w.wl(s"@implementation $baseModuleName")
      w.wl
      w.wl("-(instancetype)init").braced {
        w.wl("self = [super init];")
        w.wl("if(self)").braced {
          w.wl("self.objcImplementations = [[NSMutableDictionary alloc] init];")
          w.wl("self.implementationsData = [[NSMutableDictionary alloc] init];")
        }
        w.wl("return self;")
      }
      w.wl

      w.wl("-(void)baseRelease:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject").braced {
        w.wl("""if (!currentInstance[@"type"])""").braced {
          w.wl("""reject(@"impl_call_error", @"Invalid argument passed to release method: missing 'type' key", nil);""")
        }
        w.wl("""if (!currentInstance[@"uid"])""").braced {
          w.wl("""NSString *error = [NSString stringWithFormat:@"Error while calling release method, first argument should be an instance of %@", currentInstance[@"type"]];""")
          w.wl("""reject(@"impl_call_error", error, nil);""")
        }
        w.wl("@synchronized(self)").braced {
          w.wl("""[self.objcImplementations removeObjectForKey:currentInstance[@"uid"]];""")
        }
        w.wl("resolve(@(YES));")
      }
      w.wl

      w.wl("-(void)baseLogWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject").braced {
        w.wl("NSMutableArray *uuids = [[NSMutableArray alloc] init];")
        w.wl("for (id key in self.objcImplementations)").braced {
          w.wl("[uuids addObject:key];")
        }
        w.wl("""NSDictionary *result = @{@"value" : uuids};""")
        w.wl("resolve(result);")
      }
      w.wl

      w.wl("-(void)baseFlushWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject").braced {
        w.wl("@synchronized(self)").braced {
          w.wl("""[self.objcImplementations removeAllObjects];""")
        }
        w.wl("resolve(@(YES));")
      }
      w.wl

      w.wl("-(void)baseIsNull:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject").braced {
        w.wl("""if (!currentInstance[@"uid"] || !currentInstance[@"type"])""").braced {
          w.wl("resolve(@(YES));")
          w.wl("return;")
        }
        w.wl("@synchronized(self)").braced {
          w.wl("""if ([self.objcImplementations objectForKey:currentInstance[@"uid"]])""").braced {
            w.wl("resolve(@(NO));")
            w.wl("return;")
          }
          w.wl("else").braced {
            w.wl("resolve(@(YES));")
            w.wl("return;")
          }
        }
      }
      w.wl

      w.wl("-(void)baseSetObject:(NSArray *)object").braced {
        w.wl("//Should have first object as value and second one as key")
        w.wl("if ([object count] == 2)").braced {
          w.wl("@synchronized(self)").braced {
            w.wl("[self.objcImplementations setObject:[object objectAtIndex:0] forKey:[object objectAtIndex:1]];")
          }
        }
      }
      w.wl("@end")
    })

  }

  override def generateEnum(origin: String, ident: Ident, doc: Doc, e: Enum) {

  }


  def reactInterfaceType(tm: MExpr) : String = tm.base match {
    case MOptional => s"nullable ${reactInterfaceType(tm.args.head)}"
    case MList => s"NSArray <${reactInterfaceType(tm.args.head)}> *"
    case MSet => s"NSSet <${reactInterfaceType(tm.args.head)}> *"
    case MMap => s"NSDictionary <${reactInterfaceType(tm.args.head)}> *"
    case d: MDef =>
      d.defType match {
        case DInterface | DRecord => "NSDictionary *"
        case _ => ""
      }
    case _ => ""
  }

  def isExprInterface(tm: MExpr): Boolean = tm.base match {
    case MOptional | MList | MSet | MMap => isExprInterface(tm.args.head)
    case d: MDef =>
      d.defType match {
        case DInterface => true
        case _ => false
      }
    case _ => false
  }

  def isExprRecord(tm: MExpr): Boolean = tm.base match {
    case MOptional | MList | MSet | MMap => isExprRecord(tm.args.head)
    case d: MDef =>
      d.defType match {
        case DRecord => true
        case _ => false
      }
    case _ => false
  }

  def isExprBinary(tm: MExpr): Boolean = tm.base match {
    case MOptional | MList | MSet | MMap => isExprBinary(tm.args.head)
    case MBinary => true
    case _ => false
  }

  def isBinary(tm: MExpr): Boolean = tm.base match {
    case MOptional => isBinary(tm.args.head)
    case MBinary => true
    case _ => false
  }

  def isContainer(tm: MExpr): Boolean = tm.base match {
    case MOptional => isContainer(tm.args.head)
    case MList | MSet | MMap => true
    case _ => false
  }

  def generateParams(p: Field): Option[(String, String)] = {
    val localIdent = idObjc.local(p.ident)
    val ident = idObjc.field(p.ident)
    def generateParamsLocal(tm: MExpr, identity: String, localIdentity: String, hasParentContainer: Boolean = false): Option[(String, String)] = {
      tm.base match {
        case MMap | MList | MSet => generateParamsLocal(tm.args.head, identity, localIdentity, true)
        case MOptional => generateParamsLocal(tm.args.head, identity, localIdentity)
        case d: MDef =>
          d.defType match {
            case DInterface | DRecord => Some(identity, s"(${reactInterfaceType(p.ty.resolved)})$localIdentity")
            case DEnum => Some(identity, s"(int)$localIdentity")
          }
        case MBinary => {
          hasParentContainer match {
            case true => Some(identity, s"(NSArray<NSString *> *)$localIdentity")
            case _ => Some(identity, s"(NSString *)$localIdentity")
          }
        }
        case _ => {
          val paramType = marshal.paramType(p.ty)
          val findIntType = """int\d+_t""".r
          findIntType.findFirstIn(paramType) match {
            case Some(_) => Some(identity, s"(int)$localIdentity")
            case None => Some(identity, s"($paramType)$localIdentity")
          }
        }
      }
    }
    generateParamsLocal(p.ty.resolved, ident, localIdent)
  }


  def generateInitMethodForCallback(wr : IndentWriter, isDeclaration: Boolean = false): Unit = {
    val decl = "-(instancetype)initWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock) reject andBridge: (RCTBridge *) bridge"
    if (isDeclaration) {
      wr.wl(s"$decl;")
    } else {
      wr.wl(decl).braced {
        wr.wl("self = [super init];")
        wr.wl("if(self)").braced {
          wr.wl(s"self.resolve = resolve;")
          wr.wl(s"self.reject = reject;")
          wr.wl(s"self.bridge = bridge;")
        }
        wr.wl("return self;")
      }
    }
  }

  def generateReleaseMethod(wr : IndentWriter, objcInterface: String): Unit = {
    wr.wl("RCT_REMAP_METHOD(release, release:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)").braced {
      wr.wl("[self baseRelease:currentInstance withResolver: resolve rejecter:reject];")
    }
  }

  def generateLogInstancesMethod(wr : IndentWriter): Unit = {
    wr.wl("RCT_REMAP_METHOD(log, logWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)").braced {
      wr.wl("[self baseLogWithResolver:resolve rejecter:reject];")
    }
  }

  def generateFlushInstancesMethod(wr : IndentWriter): Unit = {
    wr.wl("RCT_REMAP_METHOD(flush, flushWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)").braced {
      wr.wl("[self baseFlushWithResolver:resolve rejecter:reject];")
    }
  }

  def generateIsNullMethod(wr : IndentWriter): Unit = {
    wr.wl("RCT_REMAP_METHOD(isNull, isNull:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)").braced {
      wr.wl("[self baseIsNull:currentInstance withResolver:resolve rejecter:reject];")
    }
  }

  def generateHexToDataMethod(wr : IndentWriter): Unit = {
    wr.wl("-(NSData *) hexStringToData: (NSString *)hexString ").braced {
      wr.wl("NSMutableData *data= [[NSMutableData alloc] init];")
      wr.wl("unsigned char byte;")
      wr.wl("char byteChars[3] = {'\\0','\\0','\\0'};")
      wr.wl("for (int i = 0; i < ([hexString length] / 2); i++)").braced {
        wr.wl("byteChars[0] = [hexString characterAtIndex: i*2];")
        wr.wl("byteChars[1] = [hexString characterAtIndex: i*2 + 1];")
        wr.wl("byte = strtol(byteChars, NULL, 16);")
        wr.wl("[data appendBytes:&byte length:1];")
      }
      wr.wl("return data;")
    }
  }

  def generateDataToHexMethod(wr : IndentWriter): Unit = {
    wr.wl("-(NSString *) dataToHexString: (NSData *)data ").braced {
      wr.wl("const unsigned char *bytes = (const unsigned char *)data.bytes;")
      wr.wl("NSMutableString *hex = [NSMutableString new];")
      wr.wl("for (NSInteger i = 0; i < data.length; i++)").braced {
        wr.wl("[hex appendFormat:@\"%02x\", bytes[i]];")
      }
      wr.wl("return [hex copy];")
    }
  }
  /*
  As always, we suppose that callbacks implement only one method: onCallback,
  it has 2 arguments, in order, first one is result and second one error
  */
  def isCallbackInterface(ident: Ident, i: Interface): Boolean = {
    if (marshal.typename(ident, i).contains("Callback")) {
      i.methods.length == 1 && i.methods.filter(m => m.ident.name == "onCallback").length > 0 && i.methods.head.params.length == 2
    } else {
      false
    }
  }

  //Allows to construct name of RCT classes (cpp or objc implemented interfaces)
  def getRCTName(paramTypeName: String) : String  = {
    if (paramTypeName.indexOf("id<") >= 0 && paramTypeName.indexOf(">") == paramTypeName.length - 1) {
      paramTypeName.slice(paramTypeName.indexOf("<") + 1, paramTypeName.indexOf(">"))
    } else {
      paramTypeName
    }
  }

  //To find out if a type is supported as return type (from React Native side)
  def isIntegerType(ty: TypeRef): Boolean = {
    ty.resolved.base match {
      case p: MPrimitive => {
        p._idlName match {
          case "i8" | "i16" | "i32" | "i64" => true
          case _ => false
        }
      }
      case _ => false
    }
  }

  def writeResultDictionary(ty: TypeRef, resultName: String, objcResultName: String, boxResult: Boolean, wr: IndentWriter) : Unit = {
    wr.w(s"""NSDictionary *$resultName = @{@"value" : """)
    val isIntType = isIntegerType(ty)
    val fieldType = if (isIntType) "NSInteger" else marshal.fieldType(ty)
    if (boxResult) {
      wr.w("@(")
    }

    def getObjcResult(tm: MExpr) : String = tm.base match {
      case p: MPrimitive => {
        fieldType match {
          case "NSNumber *" => s"@([$objcResultName intValue])"
          case _ => objcResultName
        }
      }
      //case MBinary => s"$objcResultName.description"
      case MDate => s"${objcResultName}Date"
      case MOptional => getObjcResult(tm.args.head)
      case _ => objcResultName
    }
    val objcResult = getObjcResult(ty.resolved)

    wr.w(s"$objcResult")

    if (boxResult) {
      wr.w(")")
    }
    wr.w("};")
  }

  def toReactType(tm: MExpr, converted: String, converting: String, wr: IndentWriter, isOptional: Boolean = false): Unit = tm.base match {
    case MOptional => toReactType(tm.args.head, converted, converting, wr, true)
    case MList => {
      wr.wl(s"NSMutableArray *$converted = [[NSMutableArray alloc] init];")
      wr.wl(s"for (id ${converting}_elem in $converting)").braced {
        toReactType(tm.args.head, s"${converted}_elem", s"${converting}_elem", wr, isOptional)
        wr.wl(s"[$converted addObject:${converted}_elem];")
      }
    }
    case MSet => {
      wr.wl(s"NSMutableSet *$converted = [[NSMutableSet alloc] init];")
      wr.wl(s"NSArray *arrayFromSet_$converting = [$converting allObjects];")
      wr.wl(s"for (id ${converting}_elem in arrayFromSet_$converting)").braced {
        toReactType(tm.args.head, s"${converted}_elem", s"${converting}_elem", wr, isOptional)
        wr.wl(s"[$converted addObject:${converted}_elem];")
      }
    }
    case MMap => {
      wr.wl(s"NSMutableDictionary *$converted = [[NSMutableDictionary alloc] init];")
      wr.wl(s"for (id ${converting}_key in $converting)").braced {
        wr.wl(s"id ${converted}_value = [$converting objectForKey:${converted}_key];")
        toReactType(tm.args.head, s"${converted}_value", s"${converting}_value", wr, isOptional)
        wr.wl(s"[$converted setObject:${converted}_value forKey:${converted}_key];")
      }
    }
    case MBinary => {
      wr.wl(s"NSString *$converted = [self dataToHexString:$converting];")
    }
    case d: MDef =>
      d.defType match {
        case DInterface | DRecord => {
          wr.wl(s"NSString *${converting}_uuid = [[NSUUID UUID] UUIDString];")
          //Bridge is shortning prefix if it's starting with RCT
          val prefix = "RCT"
          val objcParamType = getRCTName(marshal.typename(tm))
          val paramTypeName = spec.reactNativeTypePrefix + objcParamType
          val moduleName = if (paramTypeName.indexOf(prefix) == 0) paramTypeName.substring(prefix.length) else paramTypeName
          wr.wl(s"""$paramTypeName *rctImpl_$converting = ($paramTypeName *)[self.bridge moduleForName:@"$moduleName"];""")
          if (isOptional) {
            wr.wl(s"if ($converting)").braced {
              wr.wl(s"NSArray *${converting}_array = [[NSArray alloc] initWithObjects:$converting, ${converting}_uuid, nil];")
              wr.wl(s"[rctImpl_$converting baseSetObject:${converting}_array];")
            }
          } else {
            wr.wl(s"NSArray *${converting}_array = [[NSArray alloc] initWithObjects:$converting, ${converting}_uuid, nil];")
            wr.wl(s"[rctImpl_$converting baseSetObject:${converting}_array];")
          }
          wr.wl(s"""NSDictionary *$converted = @{@"type" : @"$moduleName", @"uid" : ${converting}_uuid };""")

        }
        case _ =>
      }
    case _ =>
  }

  def fromReactType(tm: MExpr, ident: Ident, converted: String, converting: String, wr: IndentWriter, dataContainer: String = "", hasParentContainer: Boolean = false, hasReturnValue: Boolean = true): Unit = tm.base match {
    case MOptional => fromReactType(tm.args.head, ident, converted, converting, wr, dataContainer, hasParentContainer)
    case MList => {
      wr.wl(s"NSMutableArray *$converted = [[NSMutableArray alloc] init];")
      wr.wl
      if (dataContainer.length > 0) {
        wr.wl(s"NSMutableArray *${converted}_data = [[NSMutableArray alloc] init];")
        wr.wl
      }
      wr.wl(s"for (id ${converting}_elem in $converting)").braced {
        val nextDataContainer = if (dataContainer.length > 0) s"${converted}_data" else ""
        fromReactType(tm.args.head, ident, s"${converted}_elem", s"${converting}_elem", wr, nextDataContainer, true)
        wr.wl(s"[$converted addObject:${converted}_elem];")
        wr.wl
      }
      if (dataContainer.length > 0) {
        wr.wl(s"""[$dataContainer setObject:${converted}_data forKey:@"${idJava.field(ident)}"];""")
        wr.wl
      }
    }
    case MSet => {
      wr.wl(s"NSMutableSet *$converted = [[NSMutableSet alloc] init];")
      wr.wl(s"NSArray *arrayFromSet_$converting = [$converting allObjects];")
      wr.wl
      if (dataContainer.length > 0) {
        wr.wl(s"NSMutableArray *${converted}_data = [[NSMutableArray alloc] init];")
        wr.wl
      }
      wr.wl(s"for (id ${converting}_elem in arrayFromSet_$converting)").braced {
        val nextDataContainer = if (dataContainer.length > 0) s"${converted}_data" else ""
        fromReactType(tm.args.head, ident, s"${converted}_elem", s"${converting}_elem", wr, nextDataContainer, true)
        wr.wl(s"[$converted addObject:${converted}_elem];")
      }
      if (dataContainer.length > 0) {
        wr.wl(s"""[$dataContainer setObject:${converted}_data forKey:@"${idJava.field(ident)}"];""")
        wr.wl
      }
    }
    case MMap => {
      wr.wl(s"NSMutableDictionary *$converted = [[NSMutableDictionary alloc] init];")
      wr.wl
      if (dataContainer.length > 0) {
        wr.wl(s"NSMutableArray *${converted}_data = [[NSMutableArray alloc] init];")
        wr.wl
      }
      wr.wl(s"for (id ${converting}_key in $converting)").braced {
        wr.wl(s"id ${converted}_value = [$converting objectForKey:${converted}_key];")
        val nextDataContainer = if (dataContainer.length > 0) s"${converted}_data" else ""
        fromReactType(tm.args.head, ident, s"${converted}_value", s"${converting}_value", wr, nextDataContainer, true)
        wr.wl(s"[$converted setObject:${converted}_value forKey:${converted}_key];")
      }
      if (dataContainer.length > 0) {
        wr.wl(s"""[$dataContainer setObject:${converted}_data forKey:@"${idJava.field(ident)}"];""")
        wr.wl
      }
    }
    case MBinary => {
      wr.wl(s"NSData *$converted = [self hexStringToData:$converting];")
      wr.wl
    }
    case d: MDef =>
      d.defType match {
        case DInterface | DRecord => {
          //Bridge is shortning prefix if it's starting with RCT
          val prefix = "RCT"
          val paramTypeName = marshal.typename(tm)
          val objcParamType = getRCTName(paramTypeName)
          val rctParamType = spec.reactNativeTypePrefix + objcParamType
          val isObjcImplemented = objcInterfaces.contains(objcParamType.substring(idObjc.ty("").length))
          val finalObjcParamType = s"$paramTypeName${if (isObjcImplemented) "" else " *"}"
          val moduleName = if (rctParamType.indexOf(prefix) == 0) rctParamType.substring(prefix.length) else rctParamType
          wr.wl(s"""$rctParamType *rctParam_${converting} = ($rctParamType *)[self.bridge moduleForName:@"$moduleName"];""")
          wr.wl(s"""${finalObjcParamType}$converted = ($finalObjcParamType)[rctParam_${converting}.objcImplementations objectForKey:$converting[@"uid"]];""")

          //If resolver and rejecter not used by method (method without return type),
          //we set resolver and rejecter on platform specific interfaces so we can use them
          if (!hasReturnValue && isObjcImplemented) {
            //Needs conversion to impl type
            wr.wl(s"$objcParamType${spec.reactNativeObjcImplSuffix} *${converted}_objc = ($objcParamType${spec.reactNativeObjcImplSuffix} *)$converted;")
            wr.wl(s"if (${converted}_objc)").braced {
              wr.wl(s"${converted}_objc.resolve = resolve;")
              wr.wl(s"${converted}_objc.reject = reject;")
            }
          }

          if (dataContainer.length > 0 && hasParentContainer) {
            wr.wl(s"""[$dataContainer addObject:$converting[@"uid"]];""")
          } else if (dataContainer.length > 0) {
            wr.wl(s"""[$dataContainer setObject:$converting[@"uid"] forKey:@"${idJava.field(ident)}"];""")
          }
        }
        case _ =>
      }
    case _ =>
  }
  /**
    * Generate Interface
    **/
  override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface) {
    val refs = new ReactNativeRefs()
    i.methods.map(m => {
      val addRCTHeader = true
      m.params.map(p => refs.find(p.ty, addRCTHeader))
      m.ret.foreach(r => refs.find(r, addRCTHeader))
    })
    i.consts.map(c => {
      refs.find(c.ty)
    })

    //Need for converter from hex string to NSData ?
    val needHexConverter = i.methods.filter(m => {
      m.params.filter(p => {
        p.ty.resolved.base match {
          case MList | MSet | MMap | MOptional => isBinary(p.ty.resolved.args.head)
          case _ => isBinary(p.ty.resolved)
        }
      }).length > 0 ||
        (m.ret.isDefined && (m.ret.get.resolved.base match {
          case MList | MSet | MMap | MOptional => isBinary(m.ret.get.resolved.args.head)
          case _ => isBinary(m.ret.get.resolved)
        }))
    }).length > 0

    val callbackInterface = isCallbackInterface(ident, i)
    val objcInterface = if(i.ext.objc) marshal.typename(ident, i) + spec.reactNativeObjcImplSuffix else marshal.typename(ident, i)
    val self = spec.reactNativeTypePrefix + marshal.typename(ident, i)
    refs.header.add("#import <Foundation/Foundation.h>")
    refs.header.add("#import <React/RCTBridgeModule.h>")
    refs.header.add("#import <React/RCTBridge.h>")

    val baseModuleName = spec.reactNativeTypePrefix + "BaseModule"
    if (!callbackInterface) {
      refs.header.add(s"""#import "$baseModuleName.h"""")
    }
    //Include
    val pathToObjcImpl = s""""${objcInterface}.h""""
    refs.header.add(s"#import $pathToObjcImpl")

    def writeObjcFuncDecl(method: Interface.Method, w: IndentWriter) {
      val methodIdent = idObjc.method(method.ident)
      //Special treatment for callbacks since we're using promises
      if(callbackInterface) {
        //We don't need to expose callbacks to React Native
        val label = if (method.static) "+" else "-"
        val ret = marshal.returnType(method.ret)
        val decl = s"$label ($ret)$methodIdent"
        writeAlignedObjcCall(w, decl, method.params, "", p => (idObjc.field(p.ident), s"(${marshal.paramType(p.ty)})${idObjc.local(p.ident)}"))
      } else {
        val ret = marshal.returnType(method.ret)
        val hasOnlyCallback = method.params.length == 1 && (marshal.paramType(method.params(0).ty).contains("callback") || marshal.paramType(method.params(0).ty).contains("Callback"))
        val hasNoParams = method.params.length == 0 || hasOnlyCallback
        val firstParam = s"""${if (!method.static) ":(NSDictionary *)currentInstance " else ""}"""

        val declEnd = s"$firstParam${if (hasNoParams) "" else "withParams"}"
        val decl = s"RCT_REMAP_METHOD($methodIdent,$methodIdent${declEnd}"
        writeAlignedReactNativeCall(w, decl, method.params, "", p => {
          //No callbacks
          if (!marshal.paramType(p.ty).contains("Callback")) {
            generateParams(p)
          } else {
            None
          }
        })
        val begin = if(hasNoParams) "WithResolver" else " withResolver"
        w.w(s"${begin}:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject")
      }
    }

    // Generate the header file for Interface
    val fileName = spec.reactNativeTypePrefix + marshal.headerName(ident)
    writeObjcFile(fileName, origin, refs.header, w => {
      w.wl
      writeDoc(w, doc)
      if(callbackInterface) {
        w.wl(s"@interface $self : NSObject <${marshal.typename(ident, i)}>")
        w.wl(s"@property (nonatomic, strong) RCTPromiseResolveBlock resolve;")
        w.wl(s"@property (nonatomic, strong) RCTPromiseRejectBlock reject;")
        w.wl(s"@property (nonatomic, weak) RCTBridge *bridge;")
        val isDeclaration = true
        generateInitMethodForCallback(w, isDeclaration)
        w.wl("@end")
      } else {

        w.wl(s"@interface $self : $baseModuleName <RCTBridgeModule>")
        w.wl("@end")
      }
    })

    // Generate the implementation file for Interface
    refs.body.add("#import " + q(spec.reactNativeTypePrefix + marshal.headerName(ident)))
    val implfileName = spec.reactNativeTypePrefix + idObjc.ty(ident.name) + ".m"
    writeObjcFile(implfileName, origin, refs.body, w => {
      w.wl
      w.wl(s"@implementation $self")
      if(callbackInterface) {
        generateInitMethodForCallback(w)
        //Generate hex converter
        if (needHexConverter) {
          generateDataToHexMethod(w)
        }
      } else {
        w.wl("//Export module")
        w.wl(s"RCT_EXPORT_MODULE($self)")
        w.wl
        w.wl(s"@synthesize bridge = _bridge;")
        w.wl
        //Avoid all warnings due to this method
        w.wl
        w.wl("+ (BOOL)requiresMainQueueSetup").braced {
          w.wl("return NO;")
        }
        //Release to remove objc instance from self.objcImplementations
        generateReleaseMethod(w, marshal.typename(ident, i))
        //Returns uid of all objc instances
        generateLogInstancesMethod(w)
        //Flush all objc intances from React Native Module's objcImplementations attribute
        generateFlushInstancesMethod(w)
        generateIsNullMethod(w)
        //Generate hex converter
        if (needHexConverter) {
          generateHexToDataMethod(w)
          generateDataToHexMethod(w)
        }
      }

      var hasFactoryMethod = false
      for (m <- i.methods) {
        hasFactoryMethod = hasFactoryMethod || (m.static && m.ret.isDefined && marshal.paramType(m.ret.get).equals(marshal.typename(ident, i)))
        w.wl
        writeMethodDoc(w, m, idObjc.local)
        writeObjcFuncDecl(m, w)
        w.w(s"${if (callbackInterface) "" else ")" }").braced {

          //Construct call
          val ret = marshal.returnType(m.ret)
          val boxResult = if (m.ret.isDefined) marshal.toBox(m.ret.get.resolved) else false

          if(callbackInterface) {
            //Case param is itf or container of itfs
            val errorParam = m.params(1)
            val resultParam = m.params(0)
            val isParamInterfaceOrRecord = isExprInterface(resultParam.ty.resolved) || isExprRecord(resultParam.ty.resolved)
            val isParamBinary = isBinary(resultParam.ty.resolved)
            //Case of error
            w.wl(s"if (${idObjc.field(errorParam.ident)})").braced {
              //Suppose that error has always a 'message' attribute
              w.wl(s"""self.reject(@"$self Error", ${idObjc.field(errorParam.ident)}.message, nil);""")
              w.wl("return;")
            }
            w.wl
            //TODO: Handle Enums
            if (isParamInterfaceOrRecord) {
              //Callback has result and callback args
              toReactType(resultParam.ty.resolved, "converted_result", idObjc.field(resultParam.ident), w)
              w.wl
              w.wl(s"self.resolve(converted_result);")
            } else {
              w.wl
              val boxCallbackResult = marshal.toBox(resultParam.ty.resolved)

              def getFinalResult(tm: MExpr) : String = tm.base match {
                case MBinary => {
                  w.wl(s"NSString *objcResultData = [self dataToHexString:result];")
                  "objcResultData"
                }
                case MOptional => getFinalResult(tm.args.head)
                case _ => idObjc.field(resultParam.ident)
              }
              val objcResult = getFinalResult(resultParam.ty.resolved)

              writeResultDictionary(resultParam.ty, "callbackResult", objcResult, boxCallbackResult, w)
              w.wl
              w.wl(s"self.resolve(callbackResult);")
            }
          } else {

            if (!m.static) {
              //Get current Instance
              w.wl("""if (!currentInstance[@"uid"] || !currentInstance[@"type"])""").braced {
                w.wl(s"""reject(@"impl_call_error", @"Error while calling $self::${idObjc.method(m.ident)}, first argument should be an instance of ${objcInterface}", nil);""")
                w.wl("return;")
              }

              w.wl(s"""${objcInterface} *currentInstanceObj = nil;""")
              w.wl(s"""@synchronized(self)""").braced {
                w.wl(s"""currentInstanceObj = [self.objcImplementations objectForKey:currentInstance[@"uid"]];""")
              }
              w.wl("if (!currentInstanceObj)").braced {
                w.wl(s"""NSString *error = [NSString stringWithFormat:@"Error while calling ${objcInterface}::${idObjc.method(m.ident)}, instance of uid %@ not found", currentInstance[@"uid"]];""")
                w.wl(s"""reject(@"impl_call_error", error, nil);""")
                w.wl("return;")
              }
            }

            def getConverter(param: Field, paramIndex: Int) : Unit = {
              val paramdIdentity = idObjc.field(param.ident)
              val dataContainer = ""
              val hasParentContainer = false
              def getConverterLocal(tm: MExpr) : Unit = tm.base match {
                case MOptional => getConverterLocal(tm.args.head)
                case MBinary => fromReactType(tm, param.ident, s"objcParam_$paramIndex", idObjc.field(param.ident), w, dataContainer, hasParentContainer, ret != "void")
                case MList | MSet | MMap => {
                  tm.args.head.base match {
                    case MBinary => fromReactType(tm, param.ident, s"objcParam_$paramIndex", idObjc.field(param.ident), w, dataContainer, hasParentContainer, ret != "void")
                    case d: MDef =>
                      d.defType match {
                        case DInterface | DRecord => fromReactType(tm, param.ident, s"objcParam_$paramIndex", idObjc.field(param.ident), w, dataContainer, hasParentContainer, ret != "void")
                        case _ =>
                      }
                    case _ =>
                  }
                }
                case d: MDef =>
                  d.defType match {
                    case DInterface | DRecord => fromReactType(tm, param.ident, s"objcParam_$paramIndex", idObjc.field(param.ident), w, dataContainer, hasParentContainer, ret != "void")
                    case _ =>
                  }
                case _ =>
              }
              getConverterLocal(param.ty.resolved)
            }

            //Retrieve from bridge if necessary
            m.params.foreach(p =>{
              val index = m.params.indexOf(p)
              //Bridge is shortning prefix if it's starting with RCT
              val prefix = "RCT"
              val paramTypeName = marshal.typename(p.ty)
              val objcParamType = getRCTName(paramTypeName)
              val rctParamType = spec.reactNativeTypePrefix + objcParamType
              val isObjcImplemented = objcInterfaces.contains(objcParamType.substring(idObjc.ty("").length))
              val finalObjcParamType = s"$paramTypeName${if (isObjcImplemented) "" else " *"}"
              if (paramTypeName.contains("Callback")) {
                //Construct RCT callback from resolver and rejecter
                w.wl(s"$rctParamType *objcParam_${index} = [[$rctParamType alloc] initWithResolver:resolve rejecter:reject andBridge:self.bridge];")
              } else {
                //TODO: check if parameters are having "type" and "uid" fields
                getConverter(p, index)
              }
            })

            //Start calling Objective-C method
            if (m.static || ret != "void") {
              val isIntType = isIntegerType(m.ret.get)
              val fieldType = if (isIntType) "NSInteger" else marshal.fieldType(m.ret.get)
              w.w(s"$fieldType objcResult = ")
            }
            w.w(s"[${if (m.static) objcInterface else "currentInstanceObj"} ${idObjc.method(m.ident)}")
            //Parameter call
            m.params.foreach(p =>{
              val index = m.params.indexOf(p)
              val start = if (p == m.params(0)) "" else s" ${idObjc.field(p.ident)}"
              def getParamName(tm: MExpr) : String = tm.base match {
                case MList | MSet | MMap | MOptional => getParamName(tm.args.head)
                case MBinary => s"objcParam_${index}"
                case d: MDef =>
                  d.defType match {
                    case DInterface | DRecord => s"objcParam_${index}"
                    case DEnum => s"(${marshal.fieldType(p.ty.resolved)})${idObjc.field(p.ident)}"
                  }
                case _ => idObjc.field(p.ident)
              }
              val param = getParamName(p.ty.resolved)
              w.w(s"${start}:${param}")
            })


            w.wl("];")

            // If method is sync and has void return type, it should still resolve the promise
            // unless it has no return and a specific implementation object as param
            // in that case this object will resolve the promise
            val hasCallback = m.params.exists(p => marshal.typename(p.ty).contains("Callback"));
            val hasObjcImpl = m.params.exists(p => {
              val paramTypeName = marshal.typename(p.ty.resolved)
              val objcParamType = getRCTName(paramTypeName)
              objcInterfaces.contains(objcParamType.substring(idObjc.ty("").length))
            });

            if(m.ret.isDefined) {
              //Add to implementations
              if (isExprInterface(m.ret.get.resolved) || isExprRecord(m.ret.get.resolved)) {
                //Check if it's a platform specific implementation (i.e. extCpp = true)
                //This check should rely on a more robust test, go through idls and find corresponding interface and test ?
                val paramTypeName = marshal.typename(m.ret.get)
                val objcParamType = getRCTName(paramTypeName)
                val rctReturn = spec.reactNativeTypePrefix + objcParamType
                //Bridge is shortning prefix if it's starting with RCT
                val prefix = "RCT"
                val moduleName = if (rctReturn.indexOf(prefix) == 0) rctReturn.substring(prefix.length) else rctReturn
                w.wl
                toReactType(m.ret.get.resolved, "result", "objcResult", w)
              } else {

                //If result is NSDate use NSDateFormatter
                def getFinalResult(tm: MExpr) : String = tm.base match {
                  case MDate => {
                    w.wl("NSISO8601DateFormatter *dateFormatter = [[NSISO8601DateFormatter alloc] init];")
                    w.wl("NSString *objcResultDate = [dateFormatter stringFromDate:objcResult];")
                    "objcResult"
                  }
                  case MBinary => {
                    w.wl(s"NSString *objcResultData = [self dataToHexString:objcResult];")
                    "objcResultData"
                  }
                  case MOptional => getFinalResult(tm.args.head)
                  case _ => "objcResult"
                }
                val objcResult = getFinalResult(m.ret.get.resolved)

                writeResultDictionary(m.ret.get, "result", objcResult, boxResult, w)
              }

              w.wl
              w.wl("if(result)").braced {
                w.wl("resolve(result);")
              }
              w.wl("else").braced {
                //Send a null NSError object for the moment
                w.wl(s"""reject(@"impl_call_error", @"Error while calling ${objcInterface}::${idObjc.method(m.ident)}", nil);""")
                w.wl("return;")
              }
            } else if (!hasCallback && !callbackInterface && !hasObjcImpl) {
              w.wl("resolve(@(YES));")
            }
          }
          w.wl
        }
      }

      //If no factory method, create a default one
      if (!callbackInterface && !hasFactoryMethod && i.ext.objc) {
        w.w(s"RCT_REMAP_METHOD(newInstance, newInstanceWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)").braced {
          w.wl(s"$objcInterface *objcResult = [[$objcInterface alloc] init];")
          w.wl("NSString *uuid = [[NSUUID UUID] UUIDString];")

          w.wl(s"NSArray *resultArray = [[NSArray alloc] initWithObjects:objcResult, uuid, nil];")
          w.wl(s"[self baseSetObject:resultArray];")

          val prefix = "RCT"
          val rctType = spec.reactNativeTypePrefix + objcInterface
          val moduleName = if (rctType.indexOf(prefix) == 0) rctType.substring(prefix.length) else rctType
          w.wl(s"""NSDictionary *result = @{@"type" : @"$moduleName", @"uid" : uuid };""")
          w.wl("if (!objcResult || !result)").braced {
            w.wl(s"""reject(@"impl_call_error", @"Error while calling ${rctType}::init", nil);""")
            w.wl("return;")
          }
          w.wl("resolve(result);")
        }
      }
      w.wl("@end")
    })

  }

  override def generateRecord(origin: String, ident: Ident, doc: Doc, params: Seq[TypeParam], r: Record) {
    val refs = new ReactNativeRefs()
    val addRCTHeader = true
    for (c <- r.consts)
      refs.find(c.ty)
    for (f <- r.fields)
      refs.find(f.ty, addRCTHeader)

    //Need for converter from hex string to NSData ?
    val needHexConverter = r.fields.filter(f => f.ty.resolved.base match {
      case MList | MSet | MMap | MOptional => isBinary(f.ty.resolved.args.head)
      case _ => isBinary(f.ty.resolved)
    }).length > 0

    val objcName = ident.name + (if (r.ext.objc) "_base" else "")
    val noBaseSelf = marshal.typename(ident, r) // Used for constant names
    val objcInterface = marshal.typename(objcName, r)
    val self = spec.reactNativeTypePrefix + objcInterface
    val fileName = spec.reactNativeTypePrefix + marshal.headerName(ident)

    refs.header.add("#import <Foundation/Foundation.h>")
    refs.header.add("#import <React/RCTBridgeModule.h>")
    refs.header.add("#import <React/RCTBridge.h>")

    val baseModuleName = spec.reactNativeTypePrefix + "BaseModule"
    refs.header.add(s"""#import "$baseModuleName.h"""")

    val firstInitializerArg = if(r.fields.isEmpty) "" else IdentStyle.camelUpper("with_" + r.fields.head.ident.name)

    val hasOneFieldAsInterface = r.fields.filter(f => isExprInterface(f.ty.resolved) || isExprRecord(f.ty.resolved)).length > 0

    // Generate the header file for record
    writeObjcFile(fileName, origin, refs.header, w => {
      writeDoc(w, doc)
      w.wl(s"@interface $self : $baseModuleName <RCTBridgeModule>")
      if (hasOneFieldAsInterface) {
        w.wl("-(void)mapImplementationsData:(NSDictionary *)currentInstance;")
      }
      w.wl("@end")
    })

    // Generate the implementation file for record
    val implfileName = spec.reactNativeTypePrefix + marshal.typename(ident, r) + ".m"
    writeObjcFile(implfileName, origin, refs.body, w => {
      if (r.consts.nonEmpty) generateObjcConstants(w, r.consts, noBaseSelf, ObjcConstantType.ConstVariable)

      w.wl(s"""#import "$fileName"""")
      w.wl(s"""#import "${marshal.headerName(ident)}"""")
      w.wl
      w.wl(s"@implementation $self")
      w.wl

      w.wl("//Export module")
      w.wl(s"RCT_EXPORT_MODULE($self)")
      w.wl
      w.wl(s"@synthesize bridge = _bridge;")

      //Avoid all warnings due to this method
      w.wl
      w.wl("+ (BOOL)requiresMainQueueSetup").braced {
        w.wl("return NO;")
      }

      //Release to remove objc instance from self.objcImplementations
      generateReleaseMethod(w, marshal.typename(ident, r))
      //Returns uid of all objc instances
      generateLogInstancesMethod(w)
      //Flush all objc intances from React Native Module's objcImplementations attribute
      generateFlushInstancesMethod(w)
      generateIsNullMethod(w)
      //Generate hex converter
      if (needHexConverter) {
        generateHexToDataMethod(w)
        generateDataToHexMethod(w)
      }
      // Constructor from all fields (not copying)
      val init = s"RCT_REMAP_METHOD(init, init$firstInitializerArg"
      writeAlignedReactNativeCall(w, init, r.fields, "", f => {
        generateParams(f)
      })
      val begin = if(r.fields.length == 0) "WithResolver" else " withResolver"
      w.w(s"${begin}:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)").braced {
        //Keep uuids of instances of interfaces and records

        if (hasOneFieldAsInterface) {
          w.wl(s"NSMutableDictionary *implementationsData = [[NSMutableDictionary alloc] init];")
        }
        var rejectCondition = ""

        def getParamName(field: Field, fieldIndex: Int) : String = {
          val fieldIdentity = idObjc.field(field.ident)
          def getParamNameLocal(tm: MExpr) : String = tm.base match {
            case MList | MSet | MMap | MOptional => getParamNameLocal(tm.args.head)
            case MBinary => s"field_$fieldIndex"
            case d: MDef =>
              d.defType match {
                case DInterface | DRecord => s"field_$fieldIndex"
                case DEnum => s"(${marshal.fieldType(field.ty.resolved)})${idObjc.field(field.ident)}"
              }
            case _ => fieldIdentity
          }
          getParamNameLocal(field.ty.resolved)
        }

        def getConverter(field: Field, fieldIndex: Int) : Unit = {
          val fieldIdentity = idObjc.field(field.ident)
          def getConverterLocal(tm: MExpr) : Unit = tm.base match {
            case MList | MSet | MMap | MOptional => getConverterLocal(tm.args.head)
            case MBinary => fromReactType(field.ty.resolved, field.ident, s"field_$fieldIndex", fieldIdentity, w)
            case d: MDef =>
              d.defType match {
                case DInterface | DRecord => fromReactType(field.ty.resolved, field.ident, s"field_$fieldIndex", fieldIdentity, w, "implementationsData")
                case _ =>
              }
            case _ =>
          }
          getConverterLocal(field.ty.resolved)
        }


        r.fields.map(f => {
          val id = r.fields.indexOf(f)
          val isInterface = isExprInterface(f.ty.resolved)
          val isRecord = isExprRecord(f.ty.resolved)
          val fieldIdent = idObjc.field(f.ident)
          getConverter(f, id)
          //For reject condition
          val nullability = marshal.nullability(f.ty.resolved)
          if (!nullability.isDefined || nullability.get == "nonnull") {
            val additionalCondition = getParamName(f, id)
            if (rejectCondition.length == 0) {
              rejectCondition.concat(s"!$additionalCondition")
            } else {
              rejectCondition.concat(s" || !$additionalCondition")
            }
          }
        })
        w.wl
        //Reject
        if (rejectCondition.length > 0) {
          w.wl(s"if ($rejectCondition)").braced {
            w.wl(s"""reject(@"impl_call_error", @"Error while calling $self::init", nil);""")
            w.wl("return;")
          }
        }
        w.wl
        //Resolve
        w.w(s"$objcInterface * finalResult = [[$objcInterface alloc] init$firstInitializerArg:")
        r.fields.map(f => {
          val id = r.fields.indexOf(f)
          val isInterface = isExprInterface(f.ty.resolved)
          val isRecord = isExprRecord(f.ty.resolved)
          if (id != 0) {
            w.w(s"${idObjc.field(f.ident)}:")
          }
          w.w(getParamName(f, id))
          if (id != r.fields.length - 1) {
            w.w(" ")
          }

        })
        w.w("];")
        w.wl

        w.wl("NSString *uuid = [[NSUUID UUID] UUIDString];")
        val prefix = "RCT"
        val moduleName = if (self.indexOf(prefix) == 0) self.substring(prefix.length) else self
        w.wl(s"""$self *rctImpl = ($self *)[self.bridge moduleForName:@"$moduleName"];""")

        w.wl(s"NSArray *finalResultArray = [[NSArray alloc] initWithObjects:finalResult, uuid, nil];")
        w.wl(s"[rctImpl baseSetObject:finalResultArray];")

        //w.wl(s"[rctImpl.objcImplementations setObject:finalResult forKey:uuid];")
        w.wl(s"""NSDictionary *result = @{@"type" : @"$moduleName", @"uid" : uuid };""")
        w.wl("if (result)").braced {
          if (hasOneFieldAsInterface) {
              w.wl(s"""[self.implementationsData setObject:implementationsData forKey:uuid];""")
          }
          w.wl("resolve(result);")
        }
      }
      w.wl

      //Mapping method
      if (hasOneFieldAsInterface) {
        w.wl(s"-(void)mapImplementationsData:(NSDictionary *)currentInstance").braced {
          w.wl(s"""$objcInterface *objcImpl = ($objcInterface *)[self.objcImplementations objectForKey:currentInstance[@"uid"]];""")
          w.wl("NSMutableDictionary *implementationsData = [[NSMutableDictionary alloc] init];")
          r.fields.map(f => {
            val id = r.fields.indexOf(f)
            val isFieldInterfaceOrRecord = isExprInterface(f.ty.resolved) || isExprRecord(f.ty.resolved)
            val fieldIdent = idObjc.field(f.ident)
            if (isFieldInterfaceOrRecord) {
              w.wl(s"id field_$id = objcImpl.$fieldIdent;")
              toReactType(f.ty.resolved, s"converted_field_$id", s"field_$id", w)
              w.wl(s"""[implementationsData setObject:converted_field_$id forKey:@"$fieldIdent"];""")
            }
          })
          w.wl("""[self.implementationsData setObject:implementationsData forKey:currentInstance[@"uid"]];""")
        }
      }
      //Getters attributes
      def getterResult(ty: TypeRef, returnValue: String): String = {
        val tyType = marshal.paramType(ty)
        val findIntType = """int\d+_t""".r
        findIntType.findFirstIn(tyType) match {
          case Some(_) => s"@((int)$returnValue)"
          case None => {
            if (isEnum(ty.resolved)) {
              s"@((int)$returnValue)"
            } else if (tyType.equals("BOOL")) {
              s"@($returnValue)"
            } else {
              returnValue
            }
          }
        }
      }

      def getFieldTypeName(tm: MExpr) : String = {
        tm.base match {
          case MOptional | MSet | MList | MMap => getFieldTypeName(tm.args.head)
          case _ => marshal.typename(tm)
        }
      }
      r.fields.map(f => {
        val id = r.fields.indexOf(f)
        val isFieldInterface = isExprInterface(f.ty.resolved)
        val isFieldRecord = isExprRecord(f.ty.resolved)
        val fieldIdent = idObjc.field(f.ident)
        val suffix = fieldIdent.substring(0, 1).toUpperCase() + fieldIdent.substring(1)
        //Getter
        val getterName = s"get$suffix"
        val fieldDecl = generateParams(f) match {
          case Some((ident, decl)) => decl
          case None => ""
        }
        w.wl(s"RCT_REMAP_METHOD($getterName, $getterName:(NSDictionary *)currentInstance withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)rejecter)").braced {

          val fieldTypeName = getFieldTypeName(f.ty.resolved)
          val objcFieldType = getRCTName(fieldTypeName)
          val reactFieldType = spec.reactNativeTypePrefix + objcFieldType
          val fieldType = marshal.paramType(f.ty)
          if (isFieldInterface || isFieldRecord) {
            w.wl("""NSDictionary *data = (NSDictionary *)[self.implementationsData objectForKey:currentInstance[@"uid"]];""")
            w.wl("if (!data)").braced {
              w.wl("[self mapImplementationsData:currentInstance];")
              w.wl("""data = (NSDictionary *)[self.implementationsData objectForKey:currentInstance[@"uid"]];""")
            }

            w.wl(s"""${if (isContainer(f.ty.resolved)) "NSArray<NSDictionary *>" else "NSDictionary"} *result = [data objectForKey:@"$fieldIdent"];""")
          } else {
            w.wl(s"""$objcInterface *objcImpl = ($objcInterface *)[self.objcImplementations objectForKey:currentInstance[@"uid"]];""")

            val returnValue = isBinary(f.ty.resolved) match {
              case true => {
                w.wl(s"NSString *objcImpl${fieldIdent}HexString = [self dataToHexString:objcImpl.$fieldIdent];")
                s"objcImpl${fieldIdent}HexString"
              }
              case false => s"objcImpl.$fieldIdent"
            }
            //val returnValue = s"objcImpl.$fieldIdent${if (isBinary(f.ty.resolved)) s".description" else ""}"
            w.wl(s"""NSDictionary *result = @{@"value" : ${getterResult(f.ty, returnValue)}};""")
          }

          w.wl(s"resolve(result);")
        }
        w.wl
      })

      w.wl("@end")
    })
  }

  override def writeObjcFile(fileName: String, origin: String, refs: Iterable[String], f: IndentWriter => Unit) {
    createFile(spec.reactNativeObjcOutFolder.get, fileName, (w: IndentWriter) => {
      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni from " + origin)
      w.wl
      if (refs.nonEmpty) {
        // Ignore the ! in front of each line; used to put own headers to the top
        // according to Objective-C style guide
        refs.foreach(s => w.wl(if (s.charAt(0) == '!') s.substring(1) else s))
        w.wl
      }
      f(w)
    })
  }

  def writeAlignedReactNativeCall(w: IndentWriter, call: String, params: Seq[Field], end: String, f: Field => Option[(String, String)]) = {
    w.w(call)
    val skipFirst = new SkipFirst
    params.foreach(p => {
      f(p) match {
        case Some((name, value)) =>
          skipFirst { w.wl; w.w(" " * math.max(0, call.length() - name.length)); w.w(name)  }
          w.w(":" + value)
        case _ =>
      }
    })
    w.w(end)
  }
}
